package com.atguigu.gmall.search.service.impl;

import com.atguigu.gmall.search.Goods;
import com.atguigu.gmall.search.SearchAttr;
import com.atguigu.gmall.search.repository.GoodsRepository;
import com.atguigu.gmall.search.service.SearchService;
import com.atguigu.gmall.search.vo.SearchParamVo;
import com.atguigu.gmall.search.vo.SearchResultVo;
import org.apache.commons.lang.text.StrBuilder;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.NestedQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.nested.NestedAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.nested.ParsedNested;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedLongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.core.SearchHit;
import org.springframework.data.elasticsearch.core.SearchHits;
import org.springframework.data.elasticsearch.core.document.Document;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.core.query.HighlightQuery;
import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
import org.springframework.data.elasticsearch.core.query.Query;
import org.springframework.data.elasticsearch.core.query.UpdateQuery;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * @Author:lyd
 * @Date:2023/8/26 9:27
 * @Version:1.0
 * @Description
 **/
@Service
public class SearchServiceImpl implements SearchService {
    @Autowired
    private GoodsRepository goodsRepository;
    @Autowired
    private ElasticsearchRestTemplate searchTemplate;

    @Override
    public void save(Goods goods) {
        goodsRepository.save(goods);
    }

    @Override
    public void deleteGoods(Long id) {
        goodsRepository.deleteById(id);
    }

    @Override
    public SearchResultVo search(SearchParamVo param) {
        //1、准备一个query对象。根据前端传来的条件，构建一个复杂的DSL语句，即Query对象
        Query query = buildQuery(param);
        //2、查询
        SearchHits<Goods> goods = searchTemplate.search(query, Goods.class, IndexCoordinates.of("goods"));
        //3、根据搜索到的结果，将数据封装成页面需要的VO
        SearchResultVo resultVo = buildResult(goods,param);
        return resultVo;
    }

    /**
     * 更新热度分
     *  这里我们采用更新的操作，使用别人传递过来的score，我们只负责更新就行
     * @param skuId
     * @param score
     */
    @Override
    public void incrHotScore(Long skuId, Long score) {
        Document document = Document.create();
        document.put("hotScore",score);
        //增量更新，也就是局部更新
        UpdateQuery query = UpdateQuery
                .builder(skuId.toString())
                .withDocAsUpsert(true)
                .withDocument(document)
                .build();
        searchTemplate.update(query, IndexCoordinates.of("goods"));
    }

    /**
     * 将搜索到的数据封装为页面需要的SearchResultVo
     * @param searchResult
     * @param param
     * @return
     */
    private SearchResultVo buildResult(SearchHits<Goods> searchResult,SearchParamVo param) {
        SearchResultVo result = new SearchResultVo();
        //1、设置检索参数
        result.setSearchParam(param);
        List<SearchHit<Goods>> searchHits = searchResult.getSearchHits();
        //2、设置检索到的商品数据
        List<Goods> goodsList  = searchResult.getSearchHits()
                .stream()
                .map(item -> {
                    Goods content = item.getContent();
                    //设置高亮展示
                    if (StringUtils.hasText(param.getKeyword())) {
                        String title = item.getHighlightField("title").get(0);
                        content.setTitle(title);
                    }
                    return content;
                })
                .collect(Collectors.toList());
        result.setGoodsList(goodsList);


        //3、设置品牌列表
        List<SearchResultVo.Trademark> trademarkList = new ArrayList<>();
        Map<String, Aggregation> aggAsMap = searchResult.getAggregations().getAsMap();
        //获取
        ParsedLongTerms tmIdAgg = (ParsedLongTerms) aggAsMap.get("tmIdAgg");
//        ParsedLongTerms tmIdAgg1 = searchResult.getAggregations().get("tmIdAgg");
        for (Terms.Bucket bucket : tmIdAgg.getBuckets()) {
            //获取品牌的id
            long tmId = bucket.getKeyAsNumber().longValue();
            //获取品牌的名字
            ParsedStringTerms tmNameAgg = bucket.getAggregations().get("tmNameAgg");
            String tmName = tmNameAgg.getBuckets().get(0).getKeyAsString();
            //获取品牌的logo
            ParsedStringTerms tmLogoAgg = bucket.getAggregations().get("tmLogoAgg");
            String tmLogoUrl = tmLogoAgg.getBuckets().get(0).getKeyAsString();
            SearchResultVo.Trademark trademark = new SearchResultVo.Trademark();
            trademark.setTmId(tmId);
            trademark.setTmName(tmName);
            trademark.setTmLogoUrl(tmLogoUrl);
            trademarkList.add(trademark);
        }
        result.setTrademarkList(trademarkList);

        //4、设置品牌面包屑
        if (StringUtils.hasText(param.getTrademark())) {
            String[] split = param.getTrademark().split(":");
            result.setTrademarkParam("品牌："+split[1]);
        }
        //5、回显平台属性面包屑
        if (param.getProps()!=null && param.getProps().length>0) {
            List<SearchAttr> propsParamList = new ArrayList<>();
            for (String prop : param.getProps()) {
                String[] split = prop.split((":"));
                SearchAttr searchAttr = new SearchAttr();
                searchAttr.setAttrId(Long.parseLong(split[0]));
                searchAttr.setAttrValue(split[1]);
                searchAttr.setAttrName(split[2]);
                propsParamList.add(searchAttr);
            }
            result.setPropsParamList(propsParamList);
        }
        //6、设置平台属性
        List<SearchResultVo.Attr> attrsList = new ArrayList<>();
        ParsedNested attrAgg = (ParsedNested) aggAsMap.get("attrAgg");
        ParsedLongTerms attrIdAgg = attrAgg.getAggregations().get("attrIdAgg");
        for (Terms.Bucket bucket : attrIdAgg.getBuckets()) {
            SearchResultVo.Attr attr = new SearchResultVo.Attr();
            //6.1设置平台属性
            long attrId = bucket.getKeyAsNumber().longValue();
            attr.setAttrId(attrId);

            //6.2设置平台属性名
            ParsedStringTerms attrNameAgg = bucket.getAggregations().get("attrNameAgg");
            String attrName = attrNameAgg.getBuckets().get(0).getKeyAsString();
            attr.setAttrName(attrName);

            //6.3设置平台属性值
            ParsedStringTerms attrValueAgg = bucket.getAggregations().get("attrValueAgg");
            List<String> values = attrValueAgg.getBuckets()
                    .stream()
                    .map(item -> item.getKeyAsString())
                    .collect(Collectors.toList());
            attr.setAttrValueList(values);
            attrsList.add(attr);

        }
        result.setAttrsList(attrsList);

        //7、设置返回的url地址
        //前端要求把当前访问路径要给他直接返回;两种办法:
        // 1、requestApi能直接获取到路径，获取到的东西需要自己再拼接
        // 2、完全自己造
        String urlParam = buildParam(param);
        result.setUrlParam(urlParam);

        //8、设置页码
        result.setPageNo(param.getPageNo());
        //查询到的所有数据
        long totalHits = searchResult.getTotalHits();
        //9、设置总的页码
        Long totalPages = totalHits % param.getPageSize() == 0
                ? totalHits / param.getPageSize() : totalHits / param.getPageSize() + 1;
        result.setTotalPages(totalPages);

        //10、设置排序规则
        if (StringUtils.hasText(param.getOrder())) {
            SearchResultVo.OrderMap orderMap = new SearchResultVo.OrderMap();
            String[] split = param.getOrder().split(":");
            orderMap.setType(split[0]);
            orderMap.setSort(split[1]);
            result.setOrderMap(orderMap);
        }
        return result;
    }

    /**
     * 构建前端需要的路劲参数
     * @param param
     * @return
     */
    private String buildParam(SearchParamVo param) {
        StrBuilder builder = new StrBuilder("list.html?");
        if (param.getCategory1Id()!=null) {
            builder.append("category1Id=" + param.getCategory1Id());
        }
        if (param.getCategory2Id() != null) {
            builder.append("&category2Id=" + param.getCategory2Id());
        }
        if (param.getCategory3Id() != null) {
            builder.append("&category3Id=" + param.getCategory3Id());
        }
        //关键字
        if (StringUtils.hasText(param.getKeyword())) {
            builder.append("&keyword=" + param.getKeyword());
        }
        //品牌
        if (StringUtils.hasText(param.getTrademark())) {
            builder.append("&trademark=" + param.getTrademark());
        }
        //平台属性
        if (param.getProps() != null && param.getProps().length > 0) {
            for (String prop : param.getProps()) {
                builder.append("&props=" + prop);
            }
        }
        return builder.toString();
    }

    /**
     * 构建一个query对象
     *
     * @param param
     * @return
     */
    private Query buildQuery(SearchParamVo param) {
        //1.1、构建一个boolQuery
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
        //1.2、把dsl语句改写为代码

        //1.3.1判断前端是否传入CategoryId参数,也就是分类查询条件
        if (param.getCategory1Id() != null) {
            boolQuery.must(QueryBuilders.termQuery("category1Id", param.getCategory1Id()));
        }
        if (param.getCategory2Id() != null) {
            boolQuery.must(QueryBuilders.termQuery("category2Id", param.getCategory2Id()));
        }
        if (param.getCategory3Id() != null) {
            boolQuery.must(QueryBuilders.termQuery("category3Id", param.getCategory3Id()));
        }

        //1.3.2构建品牌查询条件,判断前端是否传品牌id
        if (StringUtils.hasText(param.getTrademark())) {
            boolQuery.must(QueryBuilders.termQuery("tmId", param.getTrademark().split(":")[0]));
        }
        //1.3.3构建关键字查询条件
        if (StringUtils.hasText(param.getKeyword())) {
            boolQuery.must(QueryBuilders.matchQuery("title", param.getKeyword()));
        }

        //1.3.4构建平台属性检索条件
        if (param.getProps() != null && param.getProps().length > 0) {
            for (String prop : param.getProps()) {
                BoolQueryBuilder bool = QueryBuilders.boolQuery();
                String[] split = prop.split(":");
                //按照属性id查询
                bool.must(QueryBuilders.termQuery("attrs.attrId", split[0]));
                //按照属性值查询
                bool.must(QueryBuilders.termQuery("attrs.attrValue", split[1]));
                //构建nestedQuery
                NestedQueryBuilder nestedQuery = QueryBuilders.nestedQuery("attrs", bool, ScoreMode.None);
                //放在最外层的boolQuery里面
                boolQuery.must(nestedQuery);
            }
        }
        //1、构建dsl,也就是query对象
        NativeSearchQuery dsl = new NativeSearchQuery(boolQuery);

        //2、构建排序条件
        Sort sort = null;
        if (StringUtils.hasText(param.getOrder())) {
            String[] split = param.getOrder().split(":");
            switch (split[0]) {
                case "1":
                    sort = Sort.by("hotScore");
                    break;
                case "2":
                    sort = Sort.by("price");
            }
            sort = "asc".equals(split[1]) ? sort.ascending() : sort.descending();
            dsl.addSort(sort);
        }

        //3.构建分页条件
        Integer pageNo = param.getPageNo();
        Pageable pageable = PageRequest.of(pageNo - 1, param.getPageSize());
        dsl.setPageable(pageable);

        //4、聚合条件分析
        //4.1设置品牌id的聚合条件
        TermsAggregationBuilder tmIdAgg = AggregationBuilders.terms("tmIdAgg")
                .field("tmId")
                .size(100);
        //4.2设置品牌名字的聚合条件
        TermsAggregationBuilder tmNameAgg = AggregationBuilders.terms("tmNameAgg")
                .field("tmName")
                .size(1);
        //4.3设置品牌的logo聚合条件
        TermsAggregationBuilder tmLogoAgg = AggregationBuilders.terms("tmLogoAgg")
                .field("tmLogoUrl")
                .size(1);
        //4.4把两个聚合条件放在tmIdAgg里面
        tmIdAgg.subAggregation(tmLogoAgg);
        tmIdAgg.subAggregation(tmNameAgg);

        dsl.addAggregation(tmIdAgg);


        //5、--------------平台属性的聚合条件分析
        //5.1 nested聚合
        NestedAggregationBuilder nested = AggregationBuilders.nested("attrAgg", "attrs");
        //5.2按照attrid聚合
        TermsAggregationBuilder attrIdAgg = AggregationBuilders.terms("attrIdAgg")
                .field("attrs.attrId")
                .size(100);
        //5.3按照attrName聚合
        TermsAggregationBuilder attrNameAgg = AggregationBuilders.terms("attrNameAgg")
                .field("attrs.attrName")
                .size(1);
        //5.4按照attrValue聚合
        TermsAggregationBuilder attrValueAgg = AggregationBuilders.terms("attrValueAgg")
                .field("attrs.attrValue")
                .size(100);
        attrIdAgg.subAggregation(attrNameAgg);
        attrIdAgg.subAggregation(attrValueAgg);
        nested.subAggregation(attrIdAgg);
        //5.5使平台属性聚合生效
        dsl.addAggregation(nested);

        //6、---------------------高亮设置
        if (StringUtils.hasText(param.getKeyword())) {
            //构建高亮对象
            HighlightBuilder highlightBuilder = new HighlightBuilder();
            highlightBuilder.field("title")
                    .preTags("<span style='color:red'>")
                    .postTags("</span>");
            HighlightQuery highlightQuery = new HighlightQuery(highlightBuilder);
            dsl.setHighlightQuery(highlightQuery);
        }
        return dsl;
    }
}
